Use the new stateful records to save host, SR, network, and PIF configuration.
Add extra functionality to the SR class, to calculate capacities et al.
By Alastair Tse <atse@xensource.com>.
Signed-off-by: Ewan Mellor <ewan@xensource.com>
return check_sr_ref
+
+def valid_pif(func):
+ """Decorator to verify if sr_ref is valid before calling
+ method.
+
+ @param func: function with params: (self, session, sr_ref)
+ @rtype: callable object
+ """
+ def check_pif_ref(self, session, pif_ref, *args, **kwargs):
+ xennode = XendNode.instance()
+ if type(pif_ref) == type(str()) and pif_ref in xennode.pifs:
+ return func(self, session, pif_ref, *args, **kwargs)
+ else:
+ return xen_api_error(['PIF_HANDLE_INVALID', pif_ref])
+
+ # make sure we keep the 'api' attribute
+ if hasattr(func, 'api'):
+ check_pif_ref.api = func.api
+
+ return check_pif_ref
+
+
+
# -----------------------------
# Bridge to Legacy XM API calls
# -----------------------------
# Xen API: Class Network
# ----------------------------------------------------------------
- # TODO: NOT IMPLEMENTED
- Network_attr_ro = ['VIFs']
+ Network_attr_ro = ['VIFs', 'PIFs']
Network_attr_rw = ['name_label',
'name_description',
- 'NIC',
- 'VLAN',
'default_gateway',
'default_netmask']
+ # Xen API: Class PIF
+ # ----------------------------------------------------------------
+
+ PIF_attr_ro = ['io_read_kbs',
+ 'io_write_kbs']
+ PIF_attr_rw = ['name',
+ 'network',
+ 'host',
+ 'MAC',
+ 'MTU',
+ 'VLAN']
+
+ PIF_attr_inst = PIF_attr_rw
+
+ # object methods
+ def PIF_get_record(self, session, pif_ref):
+ node = XendNode.instance()
+ return xen_api_success(node.pifs[pif_ref].get_record())
+
+ def PIF_get_all(self, session):
+ return xen_api_success(XendNode.instance().pifs.keys())
+
+ def PIF_set_name(self, session, pif_ref, name):
+ node = XendNode.instance()
+ pif = node.pifs.get(pif_ref)
+ if pif:
+ pif.set_name(name)
+ return xen_api_void()
+
+ def PIF_set_mac(self, session, pif_ref, mac):
+ node = XendNode.instance()
+ pif = node.pifs.get(pif_ref)
+ if pif:
+ pif.set_mac(mac)
+ return xen_api_void()
+
+ def PIF_set_mtu(self, session, pif_ref, mtu):
+ node = XendNode.instance()
+ pif = node.pifs.get(pif_ref)
+ if pif:
+ pif.set_mtu(mtu)
+ return xen_api_void()
+
+ def PIF_get_mac(self, session, pif_ref):
+ node = XendNode.instance()
+ return xen_api_success(node.pifs[pif_ref].get_mac())
+
+ def PIF_get_mtu(self, session, pif_ref):
+ node = XendNode.instance()
+ return xen_api_success(node.pifs[pif_ref].get_mtu())
+
+ def PIF_get_vlan(self, session, pif_ref):
+ node = XendNode.instance()
+ return xen_api_success(node.pifs[pif_ref].get_vlan())
+
+ def PIF_get_name(self, session, pif_ref):
+ node = XendNode.instance()
+ return xen_api_success(node.pifs[pif_ref].get_name())
+
+ def PIF_get_io_read_kbs(self, session, pif_ref):
+ node = XendNode.instance()
+ return xen_api_success(node.pifs[pif_ref].get_io_read_kbs())
+
+ def PIF_get_io_write_kbs(self, session, pif_ref):
+ node = XendNode.instance()
+ return xen_api_success(node.pifs[pif_ref].get_io_write_kbs())
+
+
# Xen API: Class VM
# ----------------------------------------------------------------
return xen_api_error(XEND_ERROR_UNSUPPORTED)
def VDI_set_sharable(self, session, vdi_ref, value):
- return xen_api_todo()
+ sr = XendNode.instance().get_sr()
+ image = sr.xen_api_get_by_uuid(vdi_ref)
+ image.sharable = bool(value)
+ return xen_api_success_void()
+
def VDI_set_read_only(self, session, vdi_ref, value):
- return xen_api_todo()
+ sr = XendNode.instance().get_sr()
+ image = sr.xen_api_get_by_uuid(vdi_ref)
+ image.read_only = bool(value)
+ return xen_api_success_void()
# Object Methods
def VDI_snapshot(self, session, vdi_ref):
def SR_get_record(self, session, sr_ref):
sr = XendNode.instance().get_sr()
- return xen_api_success({
- 'uuid': sr.uuid,
- 'name_label': sr.name_label,
- 'name_description': sr.name_description,
- 'VDIs': sr.list_images(),
- 'virtual_allocation': sr.used_space_bytes(),
- 'physical_utilisation': sr.used_space_bytes(),
- 'physical_size': sr.total_space_bytes(),
- 'type': sr.type,
- 'location': sr.location
- })
+ return xen_api_success(sr.get_record())
# Attribute acceess
def SR_get_VDIs(self, session, sr_ref):
def SR_set_name_label(self, session, sr_ref, value):
sr = XendNode.instance().get_sr()
sr.name_label = value
+ XendNode.instance().save()
return xen_api_success_void()
def SR_set_name_description(self, session, sr_ref, value):
sr = XendNode.instance().get_sr()
sr.name_description = value
+ XendNode.instance().save()
return xen_api_success_void()
'VIF': (valid_vif, session_required, catch_typeerror),
'VDI': (valid_vdi, session_required, catch_typeerror),
'VTPM':(valid_vtpm, session_required, catch_typeerror),
- 'SR': (valid_sr, session_required, catch_typeerror)}
+ 'SR': (valid_sr, session_required, catch_typeerror),
+ 'PIF': (valid_pif, session_required, catch_typeerror)}
# Cheat methods
# -------------
import xen.lowlevel.xc
from xen.xend import uuid
from xen.xend.XendError import XendError
+from xen.xend.XendRoot import instance as xendroot
from xen.xend.XendStorageRepository import XendStorageRepository
+from xen.xend.XendLogging import log
+from xen.xend.XendPIF import *
+from xen.xend.XendNetwork import *
+from xen.xend.XendStateStore import XendStateStore
class XendNode:
"""XendNode - Represents a Domain 0 Host."""
def __init__(self):
- self.xc = xen.lowlevel.xc.xc()
- self.uuid = uuid.createString()
- self.cpus = {}
- self.name = socket.gethostname()
- self.desc = ""
- self.sr = XendStorageRepository()
+ """Initalises the state of all host specific objects such as
+
+ * Host
+ * Host_CPU
+ * PIF
+ * Network
+ * Storage Repository
+ """
+ self.xc = xen.lowlevel.xc.xc()
+ self.state_store = XendStateStore(xendroot().get_xend_state_path())
+
+ # load host state from XML file
+ saved_host = self.state_store.load_state('host')
+ if saved_host and len(saved_host.keys()) == 1:
+ self.uuid = saved_host.keys()[0]
+ host = saved_host[self.uuid]
+ self.name = host.get('name_label', socket.gethostname())
+ self.desc = host.get('name_description', '')
+ self.cpus = {}
+ else:
+ self.uuid = uuid.createString()
+ self.name = socket.gethostname()
+ self.desc = ''
+ self.cpus = {}
+
+ # load CPU UUIDs
+ saved_cpus = self.state_store.load_state('cpu')
+ for cpu_uuid, cpu in saved_cpus.items():
+ self.cpus[cpu_uuid] = cpu
+
+ # verify we have enough cpus here
physinfo = self.physinfo_dict()
cpu_count = physinfo['nr_cpus']
cpu_features = physinfo['hw_caps']
-
- for i in range(cpu_count):
- # construct uuid by appending extra bit on the host.
- # since CPUs belong to a host.
- cpu_uuid = self.uuid + '-%04d' % i
- cpu_info = {'uuid': cpu_uuid,
- 'host': self.uuid,
- 'number': i,
- 'features': cpu_features}
- self.cpus[cpu_uuid] = cpu_info
+
+ # If the number of CPUs don't match, we should just reinitialise
+ # the CPU UUIDs.
+ if cpu_count != len(self.cpus):
+ self.cpus = {}
+ for i in range(cpu_count):
+ cpu_uuid = uuid.createString()
+ cpu_info = {'uuid': cpu_uuid,
+ 'host': self.uuid,
+ 'number': i,
+ 'features': cpu_features}
+ self.cpus[cpu_uuid] = cpu_info
+
+ self.pifs = {}
+ self.networks = {}
+
+ # initialise PIFs
+ saved_pifs = self.state_store.load_state('pif')
+ if saved_pifs:
+ for pif_uuid, pif in saved_pifs.items():
+ self.pifs[pif_uuid] = XendPIF(pif_uuid,
+ pif['name'],
+ pif['MTU'],
+ pif['MAC'])
+ else:
+ for name, mtu, mac in linux_get_phy_ifaces():
+ pif_uuid = uuid.createString()
+ pif = XendPIF(pif_uuid, name, mtu, mac)
+ self.pifs[pif_uuid] = pif
+
+ # initialise networks
+ saved_networks = self.state_store.load_state('network')
+ if saved_networks:
+ for net_uuid, network in saved_networks.items():
+ self.networks[net_uuid] = XendNetwork(net_uuid,
+ network.get('name_label'),
+ network.get('name_description', ''),
+ network.get('default_gateway', ''),
+ network.get('default_netmask', ''))
+
+ for pif_uuid in network.get('PIFs', {}).keys():
+ pif = self.pifs.get(pif_uuid)
+ if pif:
+ self.networks.pifs[pif_uuid] = pif
+ else:
+ gateway, netmask = linux_get_default_network()
+ net_uuid = uuid.createString()
+ net = XendNetwork(net_uuid, 'net0', '', gateway, netmask)
+ self.networks[net_uuid] = net
+
+ # initialise storage
+ saved_sr = self.state_store.load_state('sr')
+ if saved_sr and len(saved_sr) == 1:
+ sr_uuid = saved_sr.keys()[0]
+ self.sr = XendStorageRepository(sr_uuid)
+ else:
+ sr_uuid = uuid.createString()
+ self.sr = XendStorageRepository(sr_uuid)
+ self.save()
+
+ def save(self):
+ # save state
+ host_record = {self.uuid: {'name_label':self.name,
+ 'name_description':self.desc}}
+ self.state_store.save_state('host',host_record)
+ self.state_store.save_state('cpu', self.cpus)
+ pif_records = dict([(k, v.get_record())
+ for k, v in self.pifs.items()])
+ self.state_store.save_state('pif', pif_records)
+ net_records = dict([(k, v.get_record())
+ for k, v in self.networks.items()])
+ self.state_store.save_state('network', net_records)
+
+
+ sr_record = {self.sr.uuid: self.sr.get_record()}
+ self.state_store.save_state('sr', sr_record)
def shutdown(self):
return 0
def notify(self, _):
return 0
-
+
+
#
# Ref validation
#
def set_description(self, new_desc):
self.desc = new_desc
+ def get_uuid(self):
+ return self.uuid
+
#
# Host CPU Functions
#
import os
import stat
import threading
+import re
+import sys
+import struct
from xen.util import mkdir
from xen.xend import uuid
from xen.xend.XendError import XendError
from xen.xend.XendVDI import *
-XEND_STORAGE_MAX_IGNORE = -1
+
+XEND_STORAGE_NO_MAXIMUM = sys.maxint
XEND_STORAGE_DIR = "/var/lib/xend/storage/"
XEND_STORAGE_QCOW_FILENAME = "%s.qcow"
XEND_STORAGE_VDICFG_FILENAME = "%s.vdi.xml"
log = logging.getLogger("xend.XendStorageRepository")
-class DeviceInvalidError(Exception):
- pass
+def qcow_virtual_size(qcow_file):
+ """Read the first 32 bytes of the QCoW header to determine its size.
+
+ See: http://www.gnome.org/~markmc/qcow-image-format.html.
+ """
+ try:
+ qcow_header = open(qcow_file, 'rb').read(32)
+ parts = struct.unpack('>IIQIIQ', qcow_header)
+ return parts[-1]
+ except IOError:
+ return -1
class XendStorageRepository:
"""A simple file backed QCOW Storage Repository.
The actual images are created in the format <uuid>.img and <uuid>.qcow.
"""
- def __init__(self, storage_dir = XEND_STORAGE_DIR,
- storage_max = XEND_STORAGE_MAX_IGNORE):
+ def __init__(self, uuid,
+ sr_type = "qcow_file",
+ name_label = "Local",
+ name_description = "Xend Storage Repository",
+ location = XEND_STORAGE_DIR,
+ storage_max = XEND_STORAGE_NO_MAXIMUM):
"""
- @keyword storage_dir: Where the images will be stored.
- @type storage_dir: string
@keyword storage_max: Maximum disk space to use in bytes.
@type storage_max: int
@type images: dictionary by image uuid.
@ivar lock: lock to provide thread safety.
"""
-
- self.storage_dir = storage_dir
- self.storage_max = storage_max
- self.storage_free = 0
- self.images = {}
# XenAPI Parameters
- self.uuid = self._sr_uuid()
- self.type = "qcow-file"
- self.location = self.storage_dir
- self.name_label = "Local"
- self.name_description = "Xend Storage Repository"
-
- self.lock = threading.RLock()
- self._refresh()
+ self.uuid = uuid
+ self.type = sr_type
+ self.location = location
+ self.name_label = name_label
+ self.name_description = name_description
+ self.images = {}
- def _sr_uuid(self):
- uuid_file = os.path.join(XEND_STORAGE_DIR, 'uuid')
- try:
- if uuid_file and os.path.exists(uuid_file):
- return open(uuid_file, 'r').read().strip()
- else:
- new_uuid = uuid.createString()
- open(uuid_file, 'w').write(new_uuid + '\n')
- return new_uuid
- except IOError:
- log.exception("Failed to determine SR UUID")
+ self.storage_max = storage_max
+ self.storage_free = 0
+ self.storage_used = 0
+ self.storage_alloc = 0
- return uuid.createString()
+ self.lock = threading.RLock()
+ self._refresh()
+
+ def get_record(self):
+ retval = {'uuid': self.uuid,
+ 'name_label': self.name_label,
+ 'name_description': self.name_description,
+ 'virtual_allocation': self.storage_alloc,
+ 'physical_utilisation': self.storage_used,
+ 'physical_size': self.storage_max,
+ 'type': self.type,
+ 'location': self.location,
+ 'VDIs': self.images.keys()}
+
+ if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
+ stfs = os.statvfs(self.location)
+ retval['physical_size'] = stfs.f_blocks * stfs.f_frsize
+ return retval
+
def _refresh(self):
"""Internal function that refreshes the state of the disk and
updates the list of images available.
"""
self.lock.acquire()
try:
- mkdir.parents(XEND_STORAGE_DIR, stat.S_IRWXU)
+ mkdir.parents(self.location, stat.S_IRWXU)
# scan the directory and populate self.images
- total_used = 0
+ virtual_alloc = 0
+ physical_used = 0
seen_images = []
- for filename in os.listdir(XEND_STORAGE_DIR):
+ for filename in os.listdir(self.location):
if filename[-5:] == XEND_STORAGE_QCOW_FILENAME[-5:]:
image_uuid = filename[:-5]
seen_images.append(image_uuid)
+
+ qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
+ cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
+ qcow_path = os.path.join(self.location, qcow_file)
+ cfg_path = os.path.join(self.location, cfg_file)
+
+ phys_size = os.stat(qcow_path).st_size
+ virt_size = qcow_virtual_size(qcow_path)
# add this image if we haven't seen it before
if image_uuid not in self.images:
- qcow_file = XEND_STORAGE_QCOW_FILENAME % image_uuid
- cfg_file = XEND_STORAGE_VDICFG_FILENAME % image_uuid
- qcow_path = os.path.join(XEND_STORAGE_DIR, qcow_file)
- cfg_path = os.path.join(XEND_STORAGE_DIR, cfg_file)
-
- qcow_size = os.stat(qcow_path).st_size
-
- # TODO: no way to stat virtual size of qcow
vdi = XendQCOWVDI(image_uuid, self.uuid,
qcow_path, cfg_path,
- qcow_size, qcow_size)
+ virt_size, phys_size)
if cfg_path and os.path.exists(cfg_path):
vdi.load_config(cfg_path)
self.images[image_uuid] = vdi
- total_used += qcow_size
+
+ physical_used += phys_size
+ virtual_alloc += virt_size
# remove images that aren't valid
for image_uuid in self.images.keys():
pass
del self.images[image_uuid]
+ self.storage_alloc = virtual_alloc
+ self.storage_used = physical_used
+
# update free storage if we have to track that
- if self.storage_max != XEND_STORAGE_MAX_IGNORE:
- self.storage_free = self.storage_max - total_used
- else:
+ if self.storage_max == XEND_STORAGE_NO_MAXIMUM:
self.storage_free = self._get_free_space()
+ else:
+ self.storage_free = self.storage_max - self.storage_alloc
finally:
self.lock.release()
@rtype: int
"""
- stfs = os.statvfs(self.storage_dir)
+ stfs = os.statvfs(self.location)
return stfs.f_bavail * stfs.f_frsize
def _has_space_available_for(self, size_bytes):
@rtype: bool
"""
- if self.storage_max != -1:
- return self.storage_free
+ if self.storage_max != XEND_STORAGE_NO_MAXIMUM:
+ return self.storage_free > size_bytes
bytes_free = self._get_free_space()
- try:
- if size_bytes < bytes_free:
- return True
- except DeviceInvalidError:
- pass
+ if size_bytes < bytes_free:
+ return True
return False
def _create_image_files(self, desired_size_bytes):
"""Create an image and return its assigned UUID.
- @param desired_size_kb: Desired image size in KB.
- @type desired_size_kb: int
+ @param desired_size_bytes: Desired image size in bytes
+ @type desired_size_bytes: int
@rtype: string
@return: uuid
raise XendError("Not enough space")
image_uuid = uuid.createString()
- qcow_path = os.path.join(XEND_STORAGE_DIR,
+ qcow_path = os.path.join(self.location,
XEND_STORAGE_QCOW_FILENAME % image_uuid)
if qcow_path and os.path.exists(qcow_path):
"""
self.lock.acquire()
try:
- if self.storage_max != XEND_STORAGE_MAX_IGNORE:
- return self.storage_max
- else:
- return self.free_space_bytes() + self.used_space_bytes()
+ return self.storage_max
finally:
self.lock.release()
# save configuration to file
cfg_filename = XEND_STORAGE_VDICFG_FILENAME % image_uuid
- cfg_path = os.path.join(XEND_STORAGE_DIR, cfg_filename)
+ cfg_path = os.path.join(self.location, cfg_filename)
image.save_config(cfg_path)
except Exception, e:
return image_uuid
- def xen_api_get_by_label(self, label):
+ def xen_api_get_by_name_label(self, label):
self.lock.acquire()
try:
- for image_uuid, val in self.images.values():
+ for image_uuid, val in self.images.items():
if val.name_label == label:
return image_uuid
return None